import { relationalStore, ValuesBucket } from "@kit.ArkData";
import { FnContext, getErrorInfo, LogTag, Option, _appError2, _appInfo2 } from "CoffeeSdk";
import { common } from "@kit.AbilityKit";
import { Table } from "./Table";
import { BdUserEntity } from "./BrickEntity/BdUserEntity";
import { readResultSetSync } from "./SearchUtil";
import { AppSelectOptions, generateSelectSqlAndArgs, SelectFun } from "./SqlUtil/SelectUtil";
import { Field, fieldTypeFromTypeString } from "./Field";
import { PragmaInfo } from "./Types";
import { generateInsertSqlAndArgs_bucket } from "./SqlUtil/InsertUtil";


const brickMap: Map<string, BrickDb> = new Map();

export class BrickDb {
  dbName: string
  rdbStore: relationalStore.RdbStore

  constructor(dbName: string) {
    this.dbName = dbName;
  }

  /**
   * 初始化 创建一个数据库连接
   * 内部得到rdb实例.
   * @param dbName 为唯一表示,和一个数据库一一对应.
   * @param context
   */
  async init(context: common.Context) {
    const STORE_CONFIG: relationalStore.StoreConfig = {
      // 数据库文件名
      name: this.dbName,
      // 数据库安全级别
      securityLevel: relationalStore.SecurityLevel.S3,
      // 可选参数，指定数据库是否加密，默认不加密
      encrypt: false,
      // 可选参数，数据库自定义路径。默认在本应用沙箱目录下创建RdbStore实例。
      customDir: 'customDir/subCustomDir',
      // 可选参数，指定数据库是否以只读方式打开。默认为false，表示数据库可读可写。为true时，只允许从数据库读取数据，不允许对数据库进行写操作，否则会返回错误码801。
      isReadOnly: false,
    };

    const rdbStore = await relationalStore.getRdbStore(context, STORE_CONFIG);
    // if (err) {
    //   _appInfo2(`create db err,${getErrorInfo(err)}`);
    //   return;
    // }
    this.rdbStore = rdbStore;

    //缓存brickDb
    brickMap.set(this.dbName, this);
    _appInfo2('create db suc');
  }

  /**
   * 创建一个表
   */
  async createTable(table: Table, c?: FnContext) {
    const sql = table.generateCreateSql();
    _appInfo2(`sql: ${sql}`, { c: c });
    await this.rdbStore.execute(sql)
      .then(() => {
        _appInfo2('create table suc', { c: c });
      })
      .catch((e: Error) => {
        _appError2(`create table fail: ${getErrorInfo(e)}`, { c: c });
      })
  }


  async getTableNames(): Promise<string[]> {
    const sql = `SELECT name FROM sqlite_master WHERE type='table'`
    const resulSet = await this.rdbStore.querySql(sql)
      .catch((e: Error) => {
        _appError2(getErrorInfo(e));
        throw e;
      })
    const buckets = readResultSetSync(resulSet)
    const names = buckets.map(v => v['name']) as string[];
    return names;
  }

  getTableStructSync(tbName: string, c?: FnContext): Table {
    const sql = `PRAGMA table_info(${tbName});`
    const resulSet = this.rdbStore.querySqlSync(sql)
    // .catch((e: Error) => {
    //   _appError2(getErrorInfo(e));
    //   throw e;
    // })
    // printResultSet(resulSet, c);
    const fields: Field[] = [];
    const infos = readResultSetSync(resulSet) as unknown as PragmaInfo[];
    for (const info of infos) {
      const ft = fieldTypeFromTypeString(info.type);
      const f = new Field(info.name, ft);
      fields.push(f);
    }
    const table = new Table(tbName);
    table.fields = fields;
    return table;
  }


  async searchBySql(o: AppSelectOptions, c?: FnContext) {
    const [sql, args] = generateSelectSqlAndArgs(o);
    const rs = await this.rdbStore.querySql(sql, args);
    return rs;
  }

  searchBySqlSync(o: AppSelectOptions, c?: FnContext): relationalStore.ResultSet {
    const [sql, args] = generateSelectSqlAndArgs(o);
    const rs = this.rdbStore.querySqlSync(sql, args);
    return rs;
  }

  countBySqlSync(tbName: string, c?: FnContext): number {
    const o: AppSelectOptions = {
      tableName: tbName,
      selectKeys: SelectFun.Count
    }
    const [sql, args] = generateSelectSqlAndArgs(o);
    const rs = this.rdbStore.querySqlSync(sql, args);
    const buckets = readResultSetSync(rs);
    const count = buckets[0]['COUNT (*)'] as number;
    return count;
  }

  async insert_bucket(tbName: string, bucket: ValuesBucket) {
    const id = await this.rdbStore.insert(tbName, bucket)
      .then(() => {
        // _appInfo2(`insert suc`, { tags: [LogTag.DbViewer] });
      })
      .catch((e: Error) => {
        _appInfo2(`insert failed:${getErrorInfo(e)}`, { tags: [LogTag.DbViewer] });
        // app.showReminder(`insert failed:${getErrorInfo(e)}`, ReminderType.Error);
      })
  }

  async insert_sql(tbName: string, bucket: ValuesBucket) {
    const [sql, args] = generateInsertSqlAndArgs_bucket(tbName, bucket);
    await this.rdbStore.executeSql(sql, args)
      .then(() => {
        // _appInfo2(`insert suc`, { tags: [LogTag.DbViewer] });
      })
      .catch((e: Error) => {
        _appInfo2(`insert failed:${getErrorInfo(e)}`, { tags: [LogTag.DbViewer] });
      })
  }

  /**
   * 测试方法,插入1w次 11s
   * @param tbName
   * @param bucket
   */
  async insert_bucket1W(tbName: string, bucket: ValuesBucket) {
    _appInfo2(`insert 1w bucket begin`, { tags: [LogTag.DbViewer] });
    for (let i = 0; i < 10000; i++) {
      const id = await this.rdbStore.insert(tbName, bucket)
        .then(() => {
          // _appInfo2(`insert suc`, { tags: [LogTag.DbViewer] });
        })
        .catch((e: Error) => {
          _appInfo2(`insert failed:${getErrorInfo(e)}`, { tags: [LogTag.DbViewer] });
          // app.showReminder(`insert failed:${getErrorInfo(e)}`, ReminderType.Error);
        })
    }
    _appInfo2(`insert 1w suc`, { tags: [LogTag.DbViewer] });
  }

  /**
   * 测试方法,插入1w次 9s多.
   * 差不多,但是bucket使用方便很多.
   * @param tbName
   * @param bucket
   */
  async insert_sql1W(tbName: string, bucket: ValuesBucket) {
    const [sql, args] = generateInsertSqlAndArgs_bucket(tbName, bucket);
    _appInfo2(`insert 1w sql begin:${sql}`, { tags: [LogTag.DbViewer] });
    for (let i = 0; i < 10000; i++) {
      await this.rdbStore.executeSql(sql, args)
        .then(() => {
          // _appInfo2(`insert suc`, { tags: [LogTag.DbViewer] });
        })
        .catch((e: Error) => {
          _appInfo2(`insert failed:${getErrorInfo(e)}`, { tags: [LogTag.DbViewer] });
        })
    }
    _appInfo2(`insert 1w suc`, { tags: [LogTag.DbViewer] });
  }

  static {
    BdUserEntity.initClass();
  }
}

export function getBrick(dbName: string): Option<BrickDb> {
  return brickMap.get(dbName);
}

